package org.microboot.validator.resolver;

import com.google.common.collect.Maps;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.microboot.core.bean.ApplicationContextHolder;
import org.microboot.core.utils.ConvertUtils;
import org.microboot.validator.entity.ValidatorEntity;
import org.microboot.validator.func.ValidatorFunc;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author 胡鹏
 */
public class ValidatorResolver {

    private final Environment environment;

    private final String prefix = "validators.";

    private final String suffix = ".json";

    private final Map<String, List<ValidatorEntity>> validatorMap = Maps.newHashMap();

    public ValidatorResolver(Environment environment) {
        this.environment = environment;
    }

    @PostConstruct
    public void init() throws Exception {
        Resource[] resources = ApplicationContextHolder.getApplicationContext().getResources("classpath*:/validators/**/*.json");
        this.resolveJsons(resources);
    }

    /**
     * 优化原因：老版本时，在ValidatorAspect中通过双层循环去解析校验规则
     * 优化目标：将双层循环校验优化成单层循环校验，在启动时就将解析过程完成
     * 大致优化思路：
     * ① 优化思路依赖于ConvertUtils.json2Map(validatorContent)解析出来的rulesMap保留了Json文件的顺序
     * ② 新建一个ValidatorEntity类，属性包括：field，message，validator，validatorFunc
     * ③ 解析校验文件，拿到文件中定义的field，message，validator，并通过validatorKey从Spring容器中拿到对应的validatorFunc
     * ④ 将field，message，validator，validatorFunc都存入ValidatorEntity实体对象，每个ValidatorEntity对象对应一个field的一条校验规则
     * ⑤ 将ValidatorEntity的List集合与Json校验文件名绑定到validatorMap中
     * ⑥ 在ValidatorAspect中通过Json校验文件名获取ValidatorEntity的List集合
     * ⑥ 遍历ValidatorEntity的List集合
     * ⑥-① 在循环外声明两个变量，分别是：currField，currValue用于记录属性名和属性值，当切换属性时，更新currField，currValue
     * ⑥-② 从ValidatorEntity中拿到field，并通过field获取属性值
     * ⑥-③ 从ValidatorEntity中拿到validatorFunc，通过validatorFunc对属性值进行校验
     *
     * @param resources
     */
    public void resolveJsons(Resource[] resources) {
        if (resources == null || resources.length == 0) {
            return;
        }
        if (MapUtils.isNotEmpty(validatorMap)) {
            return;
        }
        //用Stream代替了for循环
        validatorMap.putAll(
                Arrays.stream(resources)
                        //对文件进行过滤【判断文件后缀名是否正确】
                        .filter(resource -> StringUtils.endsWith(resource.getFilename(), suffix))
                        //遍历resources，并构建Map<String, List<ValidatorEntity>>结构【collect()类似于Spark中的action算子，即：收集】
                        .collect(Collectors.toMap(resource -> resource.getFilename(), resource -> getValidatorEntityList(resource)))
        );
    }

    public List<ValidatorEntity> getValidatorEntityList(String validatorName) {
        return validatorMap.get(validatorName);
    }

    private List<ValidatorEntity> getValidatorEntityList(Resource resource) {
        //获取json文件名
        String validatorName = resource.getFilename();
        try (InputStream in = resource.getInputStream();
             StringWriter writer = new StringWriter()) {
            //读取json文件
            IOUtils.copy(in, writer, StandardCharsets.UTF_8.name());
            //获取校验文件内容
            String validatorContent = RegExUtils.replacePattern(writer.toString(), "\\r|\\n|\\t", "");
            Assert.isTrue(StringUtils.isNotBlank(StringUtils.trim(validatorContent)),
                    "Validator error : The " + validatorName + " is empty");
            //解析校验文件内容
            Map<String, Object> validatorContentMap = ConvertUtils.json2Map(validatorContent);
            //获取校验规则
            Map<String, Object> rulesMap = MapUtils.getMap(validatorContentMap, "rules");
            Assert.isTrue(MapUtils.isNotEmpty(rulesMap),
                    "Validator error : The rules is empty in " + validatorName);
            //遍历校验规则集合，并构建List<ValidatorEntity>
            List<ValidatorEntity> validatorEntityList = rulesMap.entrySet().stream()
                    //对校验规则进行过滤【判断属性名是否为空】
                    .filter(outerEntry -> StringUtils.isNotBlank(outerEntry.getKey()))
                    //遍历并展开集合，如果用map函数，会返回一个Stream<Stream<ValidatorEntity>>的机构，因此需要展开
                    .flatMap(outerEntry -> {
                        //获取属性名
                        String field = outerEntry.getKey();
                        //获取属性校验规则
                        Map<String, Object> ruleMap = (Map<String, Object>) outerEntry.getValue();
                        //获取默认校验信息
                        String defaultMessage = MapUtils.getString(ruleMap, "message");
                        //获取校验规则项集合
                        Map<String, Object> validators = MapUtils.getMap(ruleMap, "validators");
                        Assert.isTrue(MapUtils.isNotEmpty(validators),
                                "Validator error : The validators is empty in " + validatorName);
                        //遍历校验项集合，并返回Stream<ValidatorEntity>
                        return validators.entrySet().stream()
                                //对校验项进行过滤【判断校验项的key是否为空】
                                .filter(innerEntry -> StringUtils.isNotBlank(innerEntry.getKey()))
                                //遍历校验项集合，封装校验项对象
                                .map(innerEntry -> {
                                            //获取校验项的key
                                            String validatorKey = innerEntry.getKey();
                                            //获取校验项项
                                            Map<String, Object> validator = (Map<String, Object>) innerEntry.getValue();
                                            Assert.isTrue(MapUtils.isNotEmpty(validator),
                                                    "Validator error : The validator is empty in " + validatorName);
                                            //获取校验提示信息
                                            String message = MapUtils.getString(validator, "message", defaultMessage);
                                            Assert.isTrue(StringUtils.isNotBlank(message),
                                                    "Validator error : The message is null in " + validatorName);
                                            //获取校验项对应的处理类ClassName
                                            String validatorFuncClassName = environment.getProperty(prefix.concat(validatorKey));
                                            Assert.isTrue(StringUtils.isNotBlank(validatorFuncClassName),
                                                    "Validator error : The " + validatorKey + " does not match any classes in " + validatorName);
                                            if (StringUtils.isBlank(validatorFuncClassName)) {
                                                throw new IllegalArgumentException();
                                            }
                                            //获取校验项对应的处理类对象
                                            ValidatorFunc validatorFunc = ApplicationContextHolder.getBean(validatorFuncClassName, ValidatorFunc.class);
                                            //创建校验项对象
                                            ValidatorEntity validatorEntity = new ValidatorEntity();
                                            validatorEntity.setField(field);
                                            validatorEntity.setMessage(message);
                                            validatorEntity.setValidator(validator);
                                            validatorEntity.setValidatorFunc(validatorFunc);
                                            return validatorEntity;
                                        });
                    }).collect(Collectors.toList());
            return validatorEntityList;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}